!pr0
18-Digit Arithmetic, Part 7................Bob Sander-Cederlof

Last month we began the implementation of math functions, so it seems appropriate to continue in the same direction.  This month we will reveal the LOG and EXP functions.

As always, I turned to "Computer Approximations" for some good algorithms.  I mentioned this book last month, and several of you have tried to find copies.

Thanks to Trey Johnson, of Monolith Inc. in San Antonio, for the following information:  John Wiley & Sons stopped publishing the book "Computer Approximations" in 1977.  They sold the rights to Krieger Publishing Co., and it is now being published under the same title.  Trey was quoted a price of $22.50 + shipping.  Krieger's address is P. O. Box 9542, Melbourne, FL 32901; phone is (305) 724-9542.

"Computer Approximations" is the only book I have found which lists all the actual coefficients needed to produce good approximations for the whole variety of standard functions.  Pages 189-339 are packed solid with nothing by numbers.  For example, there are ten pages of numbers for the EXP function alone, providing over 100 different approximation formulas for the EXP function.  The chapter covering EXP describes the math behind the approximations.  You pick an algorithm according to the precision you need, the number base you are using (2, 10, or whatever), the tradeoff between speed and size, and the range of arguments you will be using.  Each algorithm in the book has a number, and I indicate that number in the comments to the programs which follow.

Almost all of the approximations involve these steps:

     SIFT:  Check the argument for legal range and
            easy arguments.
     FOLD:  Reduce the range of the argument.
     POLY:  Use a polynomial or a ratio of polynomials
           to approximate the function in the reduced
           range.
   UNFOLD:  Expand the result by the reverse of the
            processes used to reduce the range.

When we first learned about logarithms in high school, we used tables in books.  One set of tables converted normal numbers to logs, and the other converted logs back to normal numbers.  The LOG function takes the place of the first set of tables, and the EXP function replaces the second.  By the way, those high school logarithms were base 10 logs.  The log of a number is the power to which you would have to raise 10 to equal the number.  For example, the log base 10 of 1000 is 3; of the square root of 10 is .5.

Scientists prefer base "e" logs.  "e" is an irrational number (as is pi) approximately equal to 2.71828182845904523536.  Did the original scientists have 2.718281828... fingers?  Maybe, if they had to chop firewood (logs?)!  Anyway, EXP and LOG in Applesoft work with base e.  LOG tells you to what power you would raise e to equal the argument, and EXP raises e to the power of the argument.

One great application of LOG and EXP is to raise any number to any power.  Applesoft (as well as DP18) has an exponentiation operator "^" for this purpose, but the code inside does it by calling on EXP and LOG.  Here are some mathematical symbols to indicate how it is done:

       let         z = x^y
      then     log z = log (x^y)
               log z = y log x
         exp (log z) = exp (y log x)
                 x^y = exp (y log x)

Here is the code for the exponentiation operator in DP18:

!lm+5
*-------------------------------------
*   EXPONENTIATION:  X ^ Y
*      (DAC) = Y
*      (ARG) = X
*-------------------------------------
DP.POWER
       JSR MOVE.DAC.TEMP3   SAVE DAC (POWER) IN TEMP3
       JSR SWAP.ARG.DAC
       JSR DP.LOG10         GET LOG X
       JSR MOVE.TEMP3.ARG   GET Y IN ARG
       JSR DMULT            Y LOG X
       JMP DP.EXP10         X ^ Y
!lm-5

Notice I used base 10 log and exp?  That is because DP18 is basically decimal.  In a binary floating point scheme such as is internal to Applesoft, base 2 log and exp would probably be used.  After all, floating point notation is a kind of half-log half-normal notation.

Which leads to the topic of converting from one logarithmic base to another.  If my internal subroutines work in base 10, how do I get LOG and EXP to base e?  Some more math is due:

       suppose         e^x = 10^y
       then    log10 (e^x) = log10 (10^y)
                x log10(e) = y log10(10)
                x log10(e) = y

Log10(e) is a constant, approximately 0.43429448190325182765.  So if I want to know what EXP(3) is, I can first get 3*log10(e) = 1.302..., and 10^1.302... = 20.0855...


EXP Function

Lines 1640-1660 of the program check for a zero argument, which is an easy case:  e^0 = 1.  Lines 1670-1700 multiply the argument by log10(e), so that EXP10 can be used.

Lines 1730-1740 again sift out the easy case of 10^0, in case DP.EXP10 was called directly.

Lines 1750-1790 begin the folding process.  We can cut the range in half by folding all negative arguments on top to the positive range:  EXP(-x) = 1/EXP(x).

Lines 1810,1820 further sift, by eliminating arguments larger than 99.  If the exponent of the argument is $43 or more, then the argument is 100 or more.  Arguments that large are too large.  (Indeed, any argument above 63 is too large.)  The Applesoft ROM routine for OVERFLOW ERROR will let you know you tried it.

The arguments we have left will be in the range 0 < x < 100.  We can further subdivide the range by separating the integer and fractional parts of the argument.  Remember that 10^(x+y) = (10^x)*(10^y)?  For illustration, suppose the argument is 3.75.  Then 10^3.75 = 10^3 * 10^.75 = 5623.4132....  Lines 1830-2100 perform the separation.  The variable INTPWR will get the integer part, which may range from 0 to 99.  The corresponding digits are zeroed in DAC, and the resulting fraction is re-normalized.  If the fractional part is zero, then the log of the fractional part is 1; lines 2080-2100 sift out this special case.  This section could be accomplished by using previously covered subroutines, such as DP.INT to get the integer part, and DSUB to get the fractional part.  However, that would take considerably longer for only a slight savings in space.

The active part of the argument has now been reduced to the range 0<x<1.  The next adjustment will cut that in half.  If the argument x<.5, this adjustment will be skipped.  Lines 2120-2160 perform the test, and line 2170 saves the result of the test on the stack.  We need the result later when we are unfolding.  If x >= .5, then lines 2190-2210 subtract .5 from it.  If x = .5, then the result after subtraction will be zero.  In this case, the correct answer is a known constant, the square root of 10.  Lines 2230-2270 load up that value and skip over the POLY part on down to the UNFOLDing.  If not exactly .5, we now have a folded argument in the range 0<x<.5, with a flag on the stack indicating whether or not we subtracted .5 to get there.  Later, if we DID subtract .5, we will multiply the result of POLY by the square root of 10 to unfold the answer.

We could have arbitrarily subtracted .5, changing the range from 0<x<1 to -.5<x<.5, with the same result.  This would have saved the trouble of determining which side of .5 we were on, and of later deciding whether or not to multiply by SQR(10).  However, it would also take longer for those cases already under .5, so I decided against it.

The POLY part is lines 2280-2520.  This is a ratio of two polynomials, both 8th degree.  However, because of derivational and computational reasons, it is actually written and calculated in a different form:

                  Q(x^2) + xP(x^2)
       POLY(x) =  ----------------
                  Q(x^2) - xP(x^2)

Lines 2290-2320 save x and compute x^2.  Lines 2330-2380 call on POLY.N (covered last month) to compute the P polynomial, and then multiply the result by x.  The constants are given in lines 1440-1490.  So that you see the form, I will give it here with the coefficients rounded off:

       xP = 31x^7 + 4562x^5 + 134331x^3 + 760254x

Lines 2400-2430 compute the Q-polynomial, by calling POLY.1 (also covered last month).  POLY.1 is used when the coefficient of the highest degreed term is 1.  We get, approximately,

       Q = x^8 + 477x^6 + 29732x^4 + 408437x^2 + 660349

Lines 2440-2520 form the numerator and denominator and divide, giving us a very nice approximation to the function for the folded argument.

Lines 2530-2590 begin the unfolding process, by multiplying by SQR(10) if we previously folded .5<x<1 down to 0<x<.5.

Lines 2600-2660 take care of the integral portion of the original argument, by adding it to the EXPONENT of the result so far.  This is equivalent to multiplying by the integral power of ten, but much faster.  Isn't base ten nice?

The final adjustment is to take the reciprocal if the original argument was negative, done in lines 2670-2730.


LOG Function

The LOG function is the inverse of the EXP function.  Now if we could just run the 6502 backwards....

Log base e is related to log base 10 the same way the exp functions were:

       loge x = loge(10) * log10 (x)

Lines 2990-3040 call on the LOG10 subroutine and then multiply the result by the log base e of 10.

The LOG10 routine begins by sifting out the objectionable argument values, at lines 3100-3130.  The argument MUST be positive, and MUST NOT be zero.  Negative or zero arguments send you to Applesoft's ILLEGAL QUANTITY ERROR.

Lines 3140-3170 separate the exponent from the mantissa of the argument.  The exponent represents the power of 10 multiplier, so as an integer it can just be added to the logarithm of the mantissa viewed as a fraction.  The exponent is saved in INTPWR, to be processed later.  Stuffing $40 in its place in DAC makes the range now .1<=x<1.

Lines 3180-3210 multiply the fraction by SQR(10), which changes the range to

       1
    -------  <= x < SQR(10)
    SQR(10)

This can be compensated for later by subtracting .5 from the logarithm of the folded argument.

Lines 3220 further thrash the argument by forming an intermediate argument z = (x-1)/(x+1).  This value z will be in the range -.52 < z < +.52, which is a nice symmetrical value to run through a ratio of polynomials.  I get lost in the math that motivates this step.

The POLY part is again a ratio of two polynomials.  Lines 3330-3440 calculate the numerator, which is approximately

     -15z^11 + 301z^9 - 1726z^7 + 4060z^5 - 4192z^3 + 1576z

The denominator, formed in lines 3450-3500, is approximately

     z^12-68z^10+764z^8-3200z^6+6122z^4-5432z^2+1815

Dividing at line 3510 gives the logarithm of the value x.  To unfold, we need to subtract .5, handled by lines 3860-3920.  We also need to add as an integer the power of ten we saved in INTPWR.  The latter is trickier, because we must convert a biased binary integer to a signed decimal floating point value.

Lines 3530-3600 un-bias INTPWR.  If the exponent happens to be exactly $40, which in un-biased terms is 0, the rest of this step can be skipped (because the log of 10^0 is zero, adding nothing).  If not, it is time to build a DP18 value in ARG.  Line 3570 saves the sign in ARG.SIGN.

Lines 3610-3620 pre-clear ARG.HI, which is where we will be putting the one or two digits of INTPWR.  Line 3630 assumes it will be a one-digit value, and lines 3640-3650 test that assumption.  If it is one digit, lines 3730-3780 will shift the digit to the left nybble and store it in ARG.HI.  If two digits, lines 3660 will divide by ten to get the high digit as quotient and low digit as remainder.  Then lines 3730-3780 will merge the two digits into ARG.HI.

Lines 3790-3840 complete the construction of ARG by storing the exponent and clearing the remaining mantissa bytes.  Line 3850 adds the value to the results of the POLY step, lines 3870-3920 subtract .5, and the answer is ready.
